﻿// Inner Fire 游戏引擎库
// DirectX主类分装 - 绘制
//
// Copyright (c) 2023 by 尔西大帝. All rights reserved.
//
// 用于绘制相关的项目
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-24
#include <dx_graphics.h>
#include <dx_scene/dx_scene.h>

namespace ifire {

void DxGraphics::Draw() {
  // 检查是否完全掌握绘制流程
  if (scene_->IsTakeoverDraw()) {
    scene_->Draw();
    return;
  }

  Draw_Reset();
  Draw_SetViewport();
  Draw_SwitchToRender();
  Draw_ClearScreen();
  Draw_SetHeaps();
  Draw_SetRootSignature();

  // 绘制场景
  scene_->Draw();

  Draw_Commit();
}

void DxGraphics::Draw_Reset() {
  // 得到当前帧的命令分配器
  auto allocator = current_frame_resource_->CmdListAlloc;
  auto cmd = command_list_.Get();

  // 复用记录命令所用的内存。只有当GPU中的命令列表执行完毕后，才能对其进行重置。
  HASSERT(allocator->Reset());

  // 通过ExecuteCommandList将命令列表加入命令队列之后，便可对其重置
  HASSERT(cmd->Reset(allocator.Get(), pipelines_["Opaque"].Get()));
}

void DxGraphics::Draw_SetViewport() {
  command_list_->RSSetViewports(1, &screen_viewport_);
  command_list_->RSSetScissorRects(1, &scissor_rect_);
}

void DxGraphics::Draw_SwitchToRender() {
  // 按照资源的用途指示其状态的转化，将资源从呈现状态转为渲染目标状态
  auto draw_barrier = CD3DX12_RESOURCE_BARRIER::Transition(GetBackBuffer(),
      D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
  command_list_->ResourceBarrier(1, &draw_barrier);
}

void DxGraphics::Draw_ClearScreen() {
  auto cmd = command_list_.Get();
  // 清除后台缓冲区和深度缓冲区
  auto depth_stencil_view = GetDepthStencilView();
  auto back_buffer = GetBackBufferView();
  cmd->ClearRenderTargetView(
      back_buffer, (float*)&main_pass_.FogColor, 0, nullptr);
  auto clear_flag = D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL;
  cmd->ClearDepthStencilView(
      depth_stencil_view, clear_flag, 1.0F, 0, 0, nullptr);

  // 指定要渲染的目标缓冲区
  cmd->OMSetRenderTargets(1, &back_buffer, true, &depth_stencil_view);
}

void DxGraphics::Draw_SetHeaps() {
   // 设置常量缓冲区（这里改了，是使用srv了）
  ID3D12DescriptorHeap* descriptor_heaps[] = {srv_descriptor_heap_.Get()};
  command_list_->SetDescriptorHeaps(
      _countof(descriptor_heaps), descriptor_heaps);
}

void DxGraphics::Draw_SetRootSignature() {
  // 设置根签名
  command_list_->SetGraphicsRootSignature(root_signature_.Get());
}

void DxGraphics::Draw_Commit() {
  // 将资源从绘制转为呈现状态
  auto present_barrier = CD3DX12_RESOURCE_BARRIER::Transition(GetBackBuffer(),
      D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
  command_list_->ResourceBarrier(1, &present_barrier);

  // 完成命令的记录
  HASSERT(command_list_->Close());

  // 将命令添加到命令队列
  ID3D12CommandList* cmd_list[] = {command_list_.Get()};
  command_queue_->ExecuteCommandLists(_countof(cmd_list), cmd_list);

  // 交换后台与前台缓冲区
  HASSERT(swap_chain_->Present(0, 0));
  current_back_buffer_ = (current_back_buffer_ + 1) % SWAP_CHAIN_BUFFER_COUNT;

  // 步进当前帧资源的围栏
  current_frame_resource_->Fence = ++current_fence_;
  // 向命令队列添加一条指令以设置一个新的栅栏点。因为我们处于GPU时间线上，
  // 新的栅栏点要等到GPU处理完所有在此Signal()之前的命令后才会设置。
  command_queue_->Signal(fence_.Get(), current_fence_);
}

void DxGraphics::DrawRenderItems(
    ID3D12GraphicsCommandList* cmd, const std::vector<RenderItem*>& items) {
  // 得到缓冲区（Uploader）
  auto object_cb = current_frame_resource_->ObjectCB->Resource();
  auto material_cb = current_frame_resource_->MaterialCB->Resource();

  // 遍历项目
  for (size_t i = 0; i < items.size(); i++) {
    // 得到项目
    auto item = items[i];

    // 绑定顶点与索引
    auto vertex_view = item->Geo->VertexBufferView();
    cmd->IASetVertexBuffers(0, 1, &vertex_view);
    auto index_view = item->Geo->IndexBufferView();
    cmd->IASetIndexBuffer(&index_view);
    cmd->IASetPrimitiveTopology(item->PrimitiveType);

    // 得到纹理的堆描述符
    CD3DX12_GPU_DESCRIPTOR_HANDLE tex(
        srv_descriptor_heap_->GetGPUDescriptorHandleForHeapStart());
    tex.Offset(item->Mat->DiffuseSrvHeapIndex, cbv_srv_uav_descriptor_size_);

    // 新式使用根常量缓冲区
    D3D12_GPU_VIRTUAL_ADDRESS obj_cb_address =
        object_cb->GetGPUVirtualAddress() +
        item->ObjCBIndex * OBJECT_CB_SIZE;
    D3D12_GPU_VIRTUAL_ADDRESS mat_cb_address =
        material_cb->GetGPUVirtualAddress() +
        item->Mat->MatCBIndex * MATERIAL_CB_SIZE;

    cmd->SetGraphicsRootDescriptorTable(0, tex);
    cmd->SetGraphicsRootConstantBufferView(1, obj_cb_address);
    cmd->SetGraphicsRootConstantBufferView(3, mat_cb_address);

    // 进行绘制
    cmd->DrawIndexedInstanced(item->IndexCount, 1, item->StartIndexLocation,
        item->BaseVertexLocation, 0);
  }
}

void DxGraphics::BuildConstantBufferViews() {
  // 得到数量
  UINT obj_count = (UINT)opaque_items_.size();

  // 循环每一个帧资源，其中每一个物体都需要一个对应的CBV描述符
  for (int frame_idx = 0; frame_idx < FRAME_RESOURCE_COUNT; frame_idx++) {
    // 得到帧资源中，用来存BUFFER的Uploader
    auto object_cb = frame_resources_[frame_idx]->ObjectCB->Resource();
    // 遍历每一个物体，将其资源按对应的索引写入
    for (UINT i = 0; i < obj_count; i++) {
      // 得到GPU的印射地址
      D3D12_GPU_VIRTUAL_ADDRESS cb_address = object_cb->GetGPUVirtualAddress();
      // 偏移到缓冲区中第i个物体的常量缓冲区
      cb_address += i * OBJECT_CB_SIZE;
      // 再偏移到物体在描述符堆中的CBV
      int heap_index = frame_idx * obj_count + i;
      auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(
          cbv_heap_->GetCPUDescriptorHandleForHeapStart());
      handle.Offset(heap_index, cbv_srv_uav_descriptor_size_);

      D3D12_CONSTANT_BUFFER_VIEW_DESC cbv_desc = {};
      cbv_desc.BufferLocation = cb_address;
      cbv_desc.SizeInBytes = OBJECT_CB_SIZE;
      device_->CreateConstantBufferView(&cbv_desc, handle);
    }
  }

  UINT pass_cb_size = dxutils::calc_constant_buffer_size(sizeof(PassConstants));

  for (int frame_idx = 0; frame_idx < FRAME_RESOURCE_COUNT; frame_idx++) {
    auto pass_cb = frame_resources_[frame_idx]->PassCB->Resource();
    // 每个帧资源的渲染过程缓冲区中只有一个常量缓冲区
    D3D12_GPU_VIRTUAL_ADDRESS cb_address = pass_cb->GetGPUVirtualAddress();
    // 偏移到描述符堆中对应的渲染过程CBV
    int heap_index = pass_cbv_offset_ + frame_idx;
    auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(
        cbv_heap_->GetCPUDescriptorHandleForHeapStart());
    handle.Offset(heap_index, cbv_srv_uav_descriptor_size_);

    D3D12_CONSTANT_BUFFER_VIEW_DESC cbv_desc = {};
    cbv_desc.BufferLocation = cb_address;
    cbv_desc.SizeInBytes = pass_cb_size;
    device_->CreateConstantBufferView(&cbv_desc, handle);
  }
}

} // namespace ifire